using System;
using System.IO;
using System.Data;
using System.Data.SqlClient;
using System.Text.RegularExpressions;
using System.Collections;
using Microsoft.TeamFoundation;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Server;
using Microsoft.TeamFoundation.VersionControl;
using Microsoft.TeamFoundation.VersionControl.Client;
using VisualStudioSensor;

namespace TeamFoundationServerSensor {

    /// <summary>
    /// Summary description for Subscribe
    /// </summary>
    public class Subscribe {

        #region Fields

        // The address that the SOAP endpoint will point to.
        string address;

        // The server where TFS is installed.
        string server;
        #endregion

        /// <summary>
        /// Use a specified location of the BiSubScribe file.
        /// </summary>
        /// <param name="address">The address the TFS should send files.</param>
        /// <param name="server">The server that houses the TFS.</param>
        public Subscribe(string address, string server) {

            // Some value checks before we continue.
            if (address == null || address.Equals(string.Empty)) {
                throw new ArgumentException("Value for address cannot be null or empty.");
            }
            else if (server == null || server.Equals(string.Empty)) {
                throw new ArgumentException("Value for server cannot be null or empty.");
            }
            else {
                this.address = address;
                this.server = server;
            }
        }

        #region Subscribe Service
        /// <summary>
        /// Overload method used to subscribe services that do not need a 
        /// filter.
        /// </summary>
        /// <param name="serviceName">The name of the service that we want to subscribe to</param>
        /// <param name="serviceURL">The url we wish to point the server to send data to.</param>
        /// <param name="filter">An optional XPath query filter.</param>
        /// <returns>The ID of the subscription if it passes.</returns>
        public int subscribeService(string serviceName, string serviceURL, string filter) {

            if (Settings.Instance.getSettingBoolean("UseIEvent")) {
                return subscribeServiceIEvent(serviceName, serviceURL, filter);
            }
            else {

                if (!FormValidator.CheckBisSubscribeTool(Settings.Instance.getSetting("BisSubscribe"))) {
                    return -1;
                }

                return subscribeServiceBisSubscribe(serviceName, serviceURL, filter); 
            }
        }

        /// <summary>
        /// Use the BisBubscribe tool to subscribe to the services.
        /// </summary>
        /// <param name="serviceName">The name of the service that we want to subscribe to</param>
        /// <param name="serviceURL">The url we wish to point the server to send data to.</param>
        /// <param name="filter">An optional filter that uses xpath.</param>
        /// <returns>The ID of the subscription if it passes.</returns>
        private int subscribeServiceBisSubscribe(string serviceName, string serviceURL, string filter) {

            Logger.TheInstance.Log("Attempting to subscribe to service: " + serviceName, LogLevel.Trace);

            // The application we wish to run. In this case it is BisSubscribe and 
            // we want to catch and store the result ID number.
            System.Diagnostics.ProcessStartInfo psi =
                new System.Diagnostics.ProcessStartInfo(Settings.Instance.getSetting("BisSubscribe"));
            psi.Arguments = (@" /eventType " + serviceName
                + @" /address " + address + serviceURL + "?op=Notify"
                + @" /deliveryType Soap"
                + @" /server " + server);

            // If a filter was specified use it.
            if (filter != null && !filter.Trim().Equals("")) {
                psi.Arguments += @" / filter" + filter;
            }

            // Hide the standard output window and redirect the output to us.
            psi.RedirectStandardOutput = true;
            psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
            psi.CreateNoWindow = true;
            psi.UseShellExecute = false;

            // Start the application and collect data.
            System.Diagnostics.Process listFiles;
            listFiles = System.Diagnostics.Process.Start(psi);
            System.IO.StreamReader myOutput = listFiles.StandardOutput;

            // Wait a maximum of 20 seconds for the application to exit.
            listFiles.WaitForExit(20000);

            // If application quit successfully parse the output for the possible
            // resulting ID which we may need later for unsubscribing.
            int value = -1;
            if (listFiles.HasExited) {
                string output = myOutput.ReadToEnd();

                try {
                    string[] temp = Regex.Split(output, @"The subscription ID is ");
                    value = int.Parse(temp.GetValue(temp.Length - 1).ToString()
                        .Trim(".\r\n".ToCharArray()));
                }
                catch (Exception e) {
                    Console.Error.WriteLine(e);
                }
            }

            if (value != -1)
                Logger.TheInstance.Log("Subscribed to service: " + serviceName, LogLevel.Info);
            else
                Logger.TheInstance.Log("Could not subscribe to service: " + serviceName, LogLevel.Info);

            return value;
        }

        /// <summary>
        /// Use the IEventService to subscribe to the services.
        /// </summary>
        /// <param name="serviceName">The name of the service that we want to subscribe to</param>
        /// <param name="serviceURL">The url we wish to point the server to send data to.</param>
        /// <param name="filter">An optional filter that uses xpath.</param>
        /// <returns>The ID of the subscription if it passes.</returns>
        private int subscribeServiceIEvent(string serviceName, string serviceURL, string filter) {

            // Connect to TFS
            TeamFoundationServer tfs = Connections.getNewTFSConnection();
            IEventService es = tfs.GetService(typeof(IEventService)) as IEventService;

            // Set Delivery Preferences
            DeliveryPreference dp = new DeliveryPreference();
            dp.Type = DeliveryType.Soap;
            dp.Schedule = DeliverySchedule.Immediate;
            dp.Address = address + serviceURL + "?op=Notify";

            // Get the user required for login.
            string userID = Settings.Instance.getSetting("TFSUser");

            return es.SubscribeEvent(userID, serviceName, filter, dp);
        }
        #endregion

        #region Remove Service
        /// <summary>
        /// Attempts to remove the service with id ID. 
        /// </summary>
        /// <param name="ID">The ID of the service we wish to remove</param>
        /// <returns>True if remove succeeded and false otherwise.</returns>
        public Boolean removeService(int ID) {

            if (Settings.Instance.getSettingBoolean("UseIEvent")) {
                return removeServiceIEvent(ID);
            }
            else {

                if (!FormValidator.CheckBisSubscribeTool(Settings.Instance.getSetting("BisSubscribe"))) {
                    return false;
                }

                return removeServiceBisSubscribe(ID);
            }
        }

        /// <summary>
        /// Attempts to remove the service with id ID using the BisSubscribe
        /// tool. 
        /// </summary>
        /// <param name="ID">The ID of the service we wish to remove</param>
        /// <returns>True if remove succeeded and false otherwise.</returns>
        private Boolean removeServiceBisSubscribe(int ID) {

            Logger.TheInstance.Log("Attempting to remove service: " + ID, LogLevel.Trace);

            Boolean success = false;
            if (ID < 0) return false;

            // The application we wish to run. In this case it is BisSubscribe and 
            // we want to catch and store the result ID number.
            System.Diagnostics.ProcessStartInfo psi =
                new System.Diagnostics.ProcessStartInfo(Settings.Instance.getSetting("BisSubscribe"));
            psi.Arguments = (@" /unsubscribe /id " + ID + @" /server " + server);

            // Hide the standard output window and redirect the output to us.
            psi.RedirectStandardOutput = true;
            psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
            psi.CreateNoWindow = true;
            psi.UseShellExecute = false;

            // Start the application and collect data.
            System.Diagnostics.Process listFiles;
            listFiles = System.Diagnostics.Process.Start(psi);
            System.IO.StreamReader myOutput = listFiles.StandardOutput;

            // Wait a maximum of 20 seconds for the application to exit.
            listFiles.WaitForExit(20000);

            // If application quit successfully parse the output for the possible
            // resulting ID which we may need later for unsubscribing.
            if (listFiles.HasExited) {
                string output = myOutput.ReadToEnd();

                try {
                    // Check the resulting output.
                    if (Regex.IsMatch(output, @"Successfuly unsubscribed subscription " + ID + ".")) {
                        success = true;
                    }
                }
                catch (Exception e) {
                    Console.Error.WriteLine(e);
                }
            }

            if (success)
                Logger.TheInstance.Log("Successfully removed service: " + ID, LogLevel.Info);
            else
                Logger.TheInstance.Log("Could not remove service: " + ID, LogLevel.Info);

            return success;
        }

        /// <summary>
        /// Attempts to remove the service with id ID using the IEventService
        /// interface. 
        /// </summary>
        /// <param name="ID"></param>
        /// <returns></returns>
        private Boolean removeServiceIEvent(int ID) {
            // Connect to TFS
            TeamFoundationServer tfs = Connections.getNewTFSConnection();
            IEventService es = tfs.GetService(typeof(IEventService)) as IEventService;

            // Remove the service
            try {
                es.UnsubscribeEvent(ID);
                return true;
            }
            catch (Exception e) {
                Logger.TheInstance.Log("Could not remove Service with ID " 
                    + ID + ". " + e, LogLevel.Error);
            }

            return false;
        }
        #endregion

        #region Helper Functions
        /// <summary>
        /// List all subscriptions to events which are being used by this address from the database.
        /// </summary>
        /// <returns>A Hastable with subscriptions as keys and urls as locations.</returns>
        public Hashtable getSubscriptions() {

            // Create a new connection object.
            SqlConnection conn = Connections.getNewSQLConnection("TfsIntegration");

            // Create a null reader for data given back from the connection.
            SqlDataReader reader = null;

            // Store results in a hash table
            Hashtable events = new Hashtable();

            try {
                // Open a connection to the server.
                conn.Open();

                // Pass the connection to a command object
                SqlCommand cmd = new SqlCommand("SELECT * FROM tbl_subscription WHERE address LIKE '"
                    + address + "%'", conn);


                // Execute and get resulting data.
                reader = cmd.ExecuteReader();

                // Read results into the hashtable
                while (reader.Read()) {
                    EventSubscription subscriptionEvent = new EventSubscription(((string)reader["event_type"]).Trim());
                    subscriptionEvent.Address = (string)reader["address"];
                    subscriptionEvent.ID = (int)reader["id"];
                    subscriptionEvent.Expression = (string)reader["expression"];

                    events.Add(subscriptionEvent.Type, subscriptionEvent);
                }

            }
            catch (Exception e) {
                Logger.TheInstance.Log("Could not retrieve subscription status. " + e, LogLevel.Error);
                events.Add("error", e);
            }
            finally {

                // Close the reader
                if (reader != null) {
                    reader.Close();
                }

                // Close the connection
                if (conn != null) {
                    conn.Close();
                }
            }

            return events;
        }
        #endregion
    }
}